#include "Sky.h"

#include <D3D10.h>
#include <DirectXMath.h>
#include <D3D10Effect.h>

#include "Camera.h"
#include "D3DErrorChecker.h"
#include "EffectManager.h"
#include "GeometryGenerator.h"
#include "InputLayoutManager.h"
#include "SkyEffect.h"
#include "TextureManager.h"

namespace D3D10Geometry
{
    Sky::Sky()
        : mDevice(nullptr)
        , mVertexBuffer(nullptr)
        , mIndexBuffer(nullptr)
        , mCubeMap(nullptr)
        , mNumIndices(0)
    {

    }

    Sky::~Sky()
    {
        mVertexBuffer->Release();
        mIndexBuffer->Release();
    }

    void Sky::init(ID3D10Device * const device, const float radius)
    {
        mDevice = device;
               
        D3D10Utils::GeometryGenerator::MeshData meshData;
        D3D10Utils::GeometryGenerator::createGeosphere(radius, 2, meshData);
                
        std::vector<DirectX::XMFLOAT3> vertices;
        vertices.reserve(meshData.mVertices.size());
        for(size_t i = 0; i < meshData.mVertices.size(); ++i)
        {
            // Scale on y-axis to turn into an ellipsoid to make a flatter Sky surface
            vertices.push_back(DirectX::XMFLOAT3(0.5f * meshData.mVertices[i].mPosition.x, 0.5f * meshData.mVertices[i].mPosition.y,
                0.5f * meshData.mVertices[i].mPosition.z));
        }

        D3D10_BUFFER_DESC vertexBufferDesc;
        vertexBufferDesc.Usage = D3D10_USAGE_IMMUTABLE;
        vertexBufferDesc.ByteWidth = sizeof(DirectX::XMFLOAT3) * static_cast<uint32_t> (vertices.size());
        vertexBufferDesc.BindFlags = D3D10_BIND_VERTEX_BUFFER;
        vertexBufferDesc.CPUAccessFlags = 0;
        vertexBufferDesc.MiscFlags = 0;

        D3D10_SUBRESOURCE_DATA initData;
        initData.pSysMem = &vertices[0];
        HRESULT hr = mDevice->CreateBuffer(&vertexBufferDesc, &initData, &mVertexBuffer);
        D3D10Utils::ErrorChecker(hr);

        mNumIndices = static_cast<uint32_t> (meshData.mIndices.size());

        D3D10_BUFFER_DESC indexBufferDesc;
        indexBufferDesc.Usage = D3D10_USAGE_IMMUTABLE;
        indexBufferDesc.ByteWidth = sizeof(uint32_t) * mNumIndices;
        indexBufferDesc.BindFlags = D3D10_BIND_INDEX_BUFFER;
        indexBufferDesc.CPUAccessFlags = 0;
        indexBufferDesc.MiscFlags = 0;

        initData.pSysMem = &meshData.mIndices[0];
        hr = mDevice->CreateBuffer(&indexBufferDesc, &initData, &mIndexBuffer);
        D3D10Utils::ErrorChecker(hr);

        // Init cube map texture resource view.
        mCubeMap = D3D10Utils::GetTextureManager().getCubeTextureRV(L"StaticCubeMapping/grassenvmap1024.dds");
    }

    void Sky::draw()
    {
        // auxMatrix0 = view matrix
        DirectX::XMMATRIX& auxMatrix0 = DirectX::XMLoadFloat4x4A(&D3D10Utils::GetCamera().getViewMatrix());
       
        // auxMatrix1 = projection matrix
        DirectX::XMMATRIX& auxMatrix1 = DirectX::XMLoadFloat4x4A(&D3D10Utils::GetCamera().getProjectionMatrix());
        
        // auxMatrix0 = view * projection
        auxMatrix0 = DirectX::XMMatrixMultiply(auxMatrix0, auxMatrix1);

        // auxMatrix1 = world
        const DirectX::XMFLOAT3A& eyePos = D3D10Utils::GetCamera().getPosition();
        auxMatrix1 = DirectX::XMMatrixTranslation(eyePos.x, eyePos.y, eyePos.z);
       
        // auxMatrix0 = world * view * projection
        auxMatrix0 = DirectX::XMMatrixMultiply(auxMatrix1, auxMatrix0);

        // Store world * view * projection into a float* casteable class
        DirectX::XMFLOAT4X4A worldViewProjection;  
        DirectX::XMStoreFloat4x4A(&worldViewProjection, auxMatrix0);

        // Set world * view * projection matrix in sky effect variable.
        D3D10Effects::EffectManager::mSkyEffect->setWorldViewProjectionMatrix(worldViewProjection);
        
        // Set cube map texture resourve view in sky effect variable.
        D3D10Effects::EffectManager::mSkyEffect->setCubeMap(mCubeMap);

        const uint32_t stride = sizeof(DirectX::XMFLOAT3);
        const uint32_t offset = 0;
        mDevice->IASetVertexBuffers(0, 1, &mVertexBuffer, &stride, &offset);
        mDevice->IASetIndexBuffer(mIndexBuffer, DXGI_FORMAT_R32_UINT, 0);
        mDevice->IASetInputLayout(D3D10InputLayouts::InputLayoutManager::mSkyInputLayout);
        mDevice->IASetPrimitiveTopology(D3D10_PRIMITIVE_TOPOLOGY_TRIANGLELIST);

        D3D10_TECHNIQUE_DESC techniqueDesc;
        ID3D10EffectTechnique* technique = D3D10Effects::EffectManager::mSkyEffect->mTechnique;
        technique->GetDesc( &techniqueDesc );

        for(unsigned __int32 p = 0; p < techniqueDesc.Passes; ++p)
        {
            ID3D10EffectPass * const pass = technique->GetPassByIndex(p);

            pass->Apply(0);
            mDevice->DrawIndexed(mNumIndices, 0, 0);
        }
    }
}
